using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using UnityEngine;
using UnityEngine.SceneManagement;

namespace Hotran.SaveSystem
{
    public class SaveManager : MonoBehaviour
    {
        public static event Action Saved;

        //����·��
        private string SaveDirectory;

        //�����ļ���ǰ׺
        private static readonly string SaveName = "save";

        //�����ļ���չ��
        private static readonly string SaveFileExt = ".data";

        //�浵Ԫ���ݵļ�
        private static readonly string SaveInfoKey = "SAVE_INFO";

        //���浵��
        private static readonly int MaxSaves = 4;

        //��ǰʹ�õĴ浵
        private static Dictionary<string, object> currentSave = null;

        private void Awake()
        {
            SaveDirectory = Application.persistentDataPath;
        }

        private string GetSaveFileName(int id) => $"{SaveName}{id.ToString()}{SaveFileExt}";

        /// <summary>
        /// �ֶ����浽�ļ�
        /// </summary>
        /// <param name="slotId">��λid</param>
        public void SaveToFile(int slotId)
        {
            SaveCurrent();

            SaveFile(currentSave, slotId);
        }

        /// <summary>
        /// ��¼��ǰ��������
        /// </summary>
        public void SaveCurrent()
        {
            if (currentSave == null)
                currentSave = new();

            CaptureState(ref currentSave);

            Saved?.Invoke();
        }

        public void LoadCurrent() => TryLoadCurrent();

        /// <summary>
        /// �ָ���ǰ��������
        /// </summary>
        public bool TryLoadCurrent()
        {
            if (currentSave != null)
            {
                RestoreState(currentSave);
                return true;
            }
            return false;
        }

        /// <summary>
        /// ��ȡ�浵����ȡ��Ӧ����
        /// </summary>
        /// <param name="slotId"></param>
        public void Load(int slotId)
        {
            currentSave = LoadFile(slotId);
            RestoreScene(currentSave);
        }

        /// <summary>
        /// ��ȡ���д浵�б���Ԫ����
        /// </summary>
        /// <returns></returns>
        public SaveInfo?[] GetSaveInfos()
        {
            var res = new SaveInfo?[MaxSaves];
            for (int i = 0; i < MaxSaves; i++)
            {
                var save = LoadFile(i);

                if (save != null && save.TryGetValue(SaveInfoKey, out object val))
                    res[i] = val as SaveInfo?;
                else res[i] = null;
            }
            return res;
        }

        //��¼��ǰ��������
        private void CaptureState(ref Dictionary<string, object> state)
        {
            foreach (var savable in FindObjectsOfType<SavableEntity>(includeInactive: true))
            {
                state[savable.Id] = savable.SaveState();
            }
            //�浵������Ϣ
            state[SaveInfoKey] = new SaveInfo()
            {
                SceneIndex = gameObject.scene.buildIndex,
                SceneName = "Level",
                Time = DateTime.Now
            };
        }

        //�ָ���ǰ��������
        private void RestoreState(Dictionary<string, object> state)
        {
            foreach (var savable in FindObjectsOfType<SavableEntity>())
            {
                if (state.TryGetValue(savable.Id, out object data))
                {
                    savable.RestoreState(data);
                }
            }
        }

        //��ȡ��Ӧ����
        private void RestoreScene(Dictionary<string, object> state)
        {
            if (state.TryGetValue(SaveInfoKey, out object val))
            {
                var data = (SaveInfo)val;
                SceneManager.LoadScene(data.SceneIndex);
            }
        }

        //���ļ���ȡ
        private Dictionary<string, object> LoadFile(int slotId)
        {
            var path = Path.Combine(SaveDirectory, GetSaveFileName(slotId));
            if (!File.Exists(path))
            {
                return null;
            }

            using var stream = File.Open(path, FileMode.Open);
            var formatter = new BinaryFormatter();
            return (Dictionary<string, object>)formatter.Deserialize(stream);
        }

        //���浽�ļ�
        private void SaveFile(Dictionary<string, object> data, int slotId)
        {
            using var stream = File.Open(Path.Combine(SaveDirectory, GetSaveFileName(slotId)),
                FileMode.Create);
            var formatter = new BinaryFormatter();
            formatter.Serialize(stream, data);
        }
    }

    /// <summary>
    /// �浵Ԫ����
    /// </summary>
    [System.Serializable]
    public struct SaveInfo
    {
        /// <summary>
        /// ����Index
        /// </summary>
        public int SceneIndex;
        /// <summary>
        /// ��ʾ�ĳ�������
        /// </summary>
        public string SceneName;
        /// <summary>
        /// �浵ʱ��
        /// </summary>
        public DateTime Time;
    }
}